// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "base/ios/crb_protocol_observers.h"

#include "base/notreached.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

@protocol TestObserver

@required
- (void)requiredMethod;
- (void)reset;

@optional
- (void)optionalMethod;
- (void)mutateByAddingObserver:(id<TestObserver>)observer;
- (void)mutateByRemovingObserver:(id<TestObserver>)observer;
- (void)nestedMutateByAddingObserver:(id<TestObserver>)observer;
- (void)nestedMutateByRemovingObserver:(id<TestObserver>)observer;

@end

// Implements only the required methods in the TestObserver protocol.
@interface TestPartialObserver : NSObject<TestObserver>
@property(nonatomic, readonly) BOOL requiredMethodInvoked;
@end

// Implements all the methods in the TestObserver protocol.
@interface TestCompleteObserver : TestPartialObserver<TestObserver>
@property(nonatomic, readonly) BOOL optionalMethodInvoked;
@end

@interface TestMutateObserver : TestCompleteObserver
- (instancetype)initWithObserver:(CRBProtocolObservers*)observer
    NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@end

namespace {

class CRBProtocolObserversTest : public PlatformTest {
 public:
  CRBProtocolObserversTest() {}

 protected:
  void SetUp() override {
    PlatformTest::SetUp();

    observers_ = (CRBProtocolObservers<TestObserver>*)[CRBProtocolObservers
        observersWithProtocol:@protocol(TestObserver)];

    partial_observer_ = [[TestPartialObserver alloc] init];
    EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);

    complete_observer_ = [[TestCompleteObserver alloc] init];
    EXPECT_FALSE([complete_observer_ requiredMethodInvoked]);
    EXPECT_FALSE([complete_observer_ optionalMethodInvoked]);

    mutate_observer_ = [[TestMutateObserver alloc] initWithObserver:observers_];
    EXPECT_FALSE([mutate_observer_ requiredMethodInvoked]);
  }

  CRBProtocolObservers<TestObserver>* observers_;
  TestPartialObserver* partial_observer_;
  TestCompleteObserver* complete_observer_;
  TestMutateObserver* mutate_observer_;
};

// Verifies basic functionality of -[CRBProtocolObservers addObserver:] and
// -[CRBProtocolObservers removeObserver:].
TEST_F(CRBProtocolObserversTest, AddRemoveObserver) {
  // Add an observer and verify that the CRBProtocolObservers instance forwards
  // an invocation to it.
  [observers_ addObserver:partial_observer_];
  [observers_ requiredMethod];
  EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);

  [partial_observer_ reset];
  EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);

  // Remove the observer and verify that the CRBProtocolObservers instance no
  // longer forwards an invocation to it.
  [observers_ removeObserver:partial_observer_];
  [observers_ requiredMethod];
  EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
}

// Verifies that CRBProtocolObservers correctly forwards the invocation of a
// required method in the protocol.
TEST_F(CRBProtocolObserversTest, RequiredMethods) {
  [observers_ addObserver:partial_observer_];
  [observers_ addObserver:complete_observer_];
  [observers_ requiredMethod];
  EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
  EXPECT_TRUE([complete_observer_ requiredMethodInvoked]);
}

// Verifies that CRBProtocolObservers correctly forwards the invocation of an
// optional method in the protocol.
TEST_F(CRBProtocolObserversTest, OptionalMethods) {
  [observers_ addObserver:partial_observer_];
  [observers_ addObserver:complete_observer_];
  [observers_ optionalMethod];
  EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
  EXPECT_FALSE([complete_observer_ requiredMethodInvoked]);
  EXPECT_TRUE([complete_observer_ optionalMethodInvoked]);
}

// Verifies that CRBProtocolObservers only holds a weak reference to an
// observer.
TEST_F(CRBProtocolObserversTest, WeakReference) {
  __weak TestPartialObserver* weak_observer = partial_observer_;
  EXPECT_TRUE(weak_observer);

  [observers_ addObserver:partial_observer_];

  // Need an autorelease pool here, because
  // -[CRBProtocolObservers forwardInvocation:] creates a temporary
  // autoreleased array that holds all the observers.
  @autoreleasepool {
    [observers_ requiredMethod];
    EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
    partial_observer_ = nil;
  }

  EXPECT_FALSE(weak_observer);
}

// Verifies that an observer can safely remove itself as observer while being
// notified.
TEST_F(CRBProtocolObserversTest, SelfMutateObservers) {
  [observers_ addObserver:mutate_observer_];
  EXPECT_FALSE([observers_ empty]);

  [observers_ requiredMethod];
  EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]);

  [mutate_observer_ reset];

  [observers_ nestedMutateByRemovingObserver:mutate_observer_];
  EXPECT_FALSE([mutate_observer_ requiredMethodInvoked]);

  [observers_ addObserver:partial_observer_];

  [observers_ requiredMethod];
  EXPECT_FALSE([mutate_observer_ requiredMethodInvoked]);
  EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);

  [observers_ removeObserver:partial_observer_];
  EXPECT_TRUE([observers_ empty]);
}

// Verifies that - [CRBProtocolObservers addObserver:] and
// - [CRBProtocolObservers removeObserver:] can be called while methods are
// being forwarded.
TEST_F(CRBProtocolObserversTest, MutateObservers) {
  // Indirectly add an observer while forwarding an observer method.
  [observers_ addObserver:mutate_observer_];

  [observers_ mutateByAddingObserver:partial_observer_];
  EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);

  // Check that methods are correctly forwared to the indirectly added observer.
  [mutate_observer_ reset];
  [observers_ requiredMethod];
  EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]);
  EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);

  [mutate_observer_ reset];
  [partial_observer_ reset];

  // Indirectly remove an observer while forwarding an observer method.
  [observers_ mutateByRemovingObserver:partial_observer_];

  // Check that method is not forwared to the indirectly removed observer.
  [observers_ requiredMethod];
  EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]);
  EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
}

// Verifies that - [CRBProtocolObservers addObserver:] and
// - [CRBProtocolObservers removeObserver:] can be called while methods are
// being forwarded with a nested invocation depth > 0.
TEST_F(CRBProtocolObserversTest, NestedMutateObservers) {
  // Indirectly add an observer while forwarding an observer method.
  [observers_ addObserver:mutate_observer_];

  [observers_ nestedMutateByAddingObserver:partial_observer_];
  EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);

  // Check that methods are correctly forwared to the indirectly added observer.
  [mutate_observer_ reset];
  [observers_ requiredMethod];
  EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]);
  EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);

  [mutate_observer_ reset];
  [partial_observer_ reset];

  // Indirectly remove an observer while forwarding an observer method.
  [observers_ nestedMutateByRemovingObserver:partial_observer_];

  // Check that method is not forwared to the indirectly removed observer.
  [observers_ requiredMethod];
  EXPECT_TRUE([mutate_observer_ requiredMethodInvoked]);
  EXPECT_FALSE([partial_observer_ requiredMethodInvoked]);
}

// Verifies that CRBProtocolObservers works if an observer deallocs.
TEST_F(CRBProtocolObserversTest, IgnoresDeallocedObservers) {
  __weak TestPartialObserver* weak_observer = partial_observer_;
  EXPECT_TRUE(weak_observer);

  [observers_ addObserver:partial_observer_];

  // Need an autorelease pool here, because
  // -[CRBProtocolObservers forwardInvocation:] creates a temporary
  // autoreleased array that holds all the observers.
  @autoreleasepool {
    [observers_ requiredMethod];
    EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
    partial_observer_ = nil;
  }

  EXPECT_FALSE(weak_observer);
  // This shouldn't crash.
  [observers_ requiredMethod];
}

}  // namespace

@implementation TestPartialObserver {
  BOOL _requiredMethodInvoked;
}

- (BOOL)requiredMethodInvoked {
  return _requiredMethodInvoked;
}

- (void)requiredMethod {
  _requiredMethodInvoked = YES;
}

- (void)reset {
  _requiredMethodInvoked = NO;
}

@end

@implementation TestCompleteObserver {
  BOOL _optionalMethodInvoked;
}

- (BOOL)optionalMethodInvoked {
  return _optionalMethodInvoked;
}

- (void)optionalMethod {
  _optionalMethodInvoked = YES;
}

- (void)reset {
  [super reset];
  _optionalMethodInvoked = NO;
}

@end

@implementation TestMutateObserver {
  __weak id _observers;
}

- (instancetype)initWithObserver:(CRBProtocolObservers*)observers {
  self = [super init];
  if (self) {
    _observers = observers;
  }
  return self;
}

- (instancetype)init {
  NOTREACHED();
  return nil;
}

- (void)mutateByAddingObserver:(id<TestObserver>)observer {
  [_observers addObserver:observer];
}

- (void)mutateByRemovingObserver:(id<TestObserver>)observer {
  [_observers removeObserver:observer];
}

- (void)nestedMutateByAddingObserver:(id<TestObserver>)observer {
  [_observers mutateByAddingObserver:observer];
}

- (void)nestedMutateByRemovingObserver:(id<TestObserver>)observer {
  [_observers mutateByRemovingObserver:observer];
}

@end
